package top.dyzmj.detty.core.buffer;

import top.dyzmj.detty.core.exception.IllegalReferenceCountException;
import top.dyzmj.detty.core.utils.ByteBufUtil;
import top.dyzmj.detty.core.utils.ObjectUtil;
import top.dyzmj.detty.core.utils.StringUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;

/**
 * 描述: 缓冲区的骨架实现
 *
 * @author dongYu
 * @date 2021/11/16
 */
public abstract class AbstractByteBuf extends ByteBuf {

	/**
	 * 最大容量
	 */
	protected int maxCapacity;
	protected ByteBuffer byteBuffer;
	/**
	 * 读下标
	 */
	int readerIndex;
	/**
	 * 写下标
	 */
	int writerIndex;
	/**
	 * 被标记的读下标
	 */
	private int markedReaderIndex;
	/**
	 * 被标记的写下标
	 */
	private int markedWriterIndex;

	/**
	 * 构造方法
	 *
	 * @param maxCapacity 最大容量
	 */
	protected AbstractByteBuf(int maxCapacity) {
		if (maxCapacity < 0) {
			throw new IllegalArgumentException("maxCapacity: " + maxCapacity + " (expected: >= 0)");
		}
		this.maxCapacity = maxCapacity;
	}

	private static void checkIndexBounds(final int readerIndex, final int writerIndex, final int capacity) {
		if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > capacity) {
			throw new IndexOutOfBoundsException(String.format(
					"readerIndex: %d, writerIndex: %d (expected: 0 <= readerIndex <= writerIndex <= capacity(%d))",
					readerIndex, writerIndex, capacity));
		}
	}

	private static void checkRangeBounds(final String indexName, final int index,
										 final int fieldLength, final int capacity) {
		if (isOutOfBounds(index, fieldLength, capacity)) {
			throw new IndexOutOfBoundsException(String.format(
					"%s: %d, length: %d (expected: range(0, %d))", indexName, index, fieldLength, capacity));
		}
	}

	/**
	 * 确定请求的索引和长度是否适合容量
	 *
	 * @param index    起始索引
	 * @param length   要使用的长度(从索引开始)。
	 * @param capacity index + length允许的容量范围。
	 * @return 如果请求的索引和长度符合容量，则为False。如果这会导致索引越界异常，则为True
	 */
	public static boolean isOutOfBounds(int index, int length, int capacity) {
		return (index | length | capacity | (index + length) | (capacity - (index + length))) < 0;
	}

	@Override
	public int maxCapacity() {
		return maxCapacity;
	}

	protected final void maxCapacity(int maxCapacity) {
		this.maxCapacity = maxCapacity;
	}

	@Override
	public int readerIndex() {
		return readerIndex;
	}

	@Override
	public ByteBuf readerIndex(int readerIndex) {
		checkIndexBounds(readerIndex, writerIndex, capacity());
		this.readerIndex = readerIndex;
		return this;
	}

	@Override
	public int writerIndex() {
		return writerIndex;
	}

	@Override
	public ByteBuf writerIndex(int writerIndex) {
		checkIndexBounds(readerIndex, writerIndex, capacity());
		this.writerIndex = writerIndex;
		return this;
	}

	@Override
	public ByteBuf setIndex(int readerIndex, int writerIndex) {
		checkIndexBounds(readerIndex, writerIndex, capacity());
		this.readerIndex = readerIndex;
		this.writerIndex = writerIndex;
		return this;
	}

	@Override
	public ByteBuf clear() {
		readerIndex = writerIndex = 0;
		return this;
	}

	@Override
	public boolean isReadable() {
		return writerIndex > readerIndex;
	}

	@Override
	public boolean isReadable(int size) {
		return writerIndex - readerIndex > size;
	}

	@Override
	public boolean isWritable() {
		return capacity() > writerIndex;
	}

	@Override
	public boolean isWritable(int size) {
		return capacity() - writerIndex > size;
	}

	@Override
	public int readableBytes() {
		return writerIndex - readerIndex;
	}

	@Override
	public int writableBytes() {
		return capacity() - writerIndex;
	}

	@Override
	public int maxWritableBytes() {
		return maxCapacity() - writerIndex;
	}

	@Override
	public byte getByte(int index) {
		checkInt(index);
		return _getByte(index);
	}

	protected abstract byte _getByte(int index);

	@Override
	public int getInt(int index) {
		checkInt(index);
		return _getInt(index);
	}

	protected abstract int _getInt(int index);

	@Override
	public long getLong(int index) {
		checkIndex(index, 8);
		return _getLong(index);
	}

	protected abstract long _getLong(int index);

	@Override
	public ByteBuf getBytes(int index, ByteBuf dst) {
		getBytes(index, dst, dst.writableBytes());
		return this;
	}

	@Override
	public ByteBuf getBytes(int index, ByteBuf dst, int length) {
		getBytes(index, dst, dst.writerIndex(), length);
		dst.writerIndex(dst.writerIndex() + length);
		return this;
	}

	@Override
	public ByteBuf setByte(int index, int value) {
		checkInt(index);
		_setByte(index, value);
		return null;
	}

	protected abstract void _setByte(int index, int value);

	@Override
	public ByteBuf setInt(int index, int value) {
		checkIndex(index, 4);
		_setInt(index, value);
		return this;
	}

	protected abstract void _setInt(int index, int value);

	@Override
	public ByteBuf setBytes(int index, ByteBuf src) {
		setBytes(index, src, src.readableBytes());
		return this;
	}

	@Override
	public ByteBuf setBytes(int index, ByteBuf src, int length) {
		checkIndex(index, length);

		ObjectUtil.checkNotNull(src, "src");

		if (length > src.readableBytes()) {
			throw new IndexOutOfBoundsException(String.format(
					"length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
		}

		setBytes(index, src, src.readerIndex(), length);
		src.readerIndex(src.readerIndex() + length);
		return this;
	}

	@Override
	public byte readByte() {
		checkReadableBytes0(1);
		int i = readerIndex;
		byte b = getByte(i);
		readerIndex = i + 1;
		return b;
	}

	@Override
	public int readInt() {
		checkReadableBytes0(4);
		int v = _getInt(readerIndex);
		readerIndex += 4;
		return v;
	}

	@Override
	public ByteBuf readBytes(byte[] dst) {
		readBytes(dst, 0, dst.length);
		return this;
	}

	@Override
	public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
		checkReadableBytes0(length);
		getBytes(readerIndex, dst, dstIndex, length);
		readerIndex += length;
		return this;
	}

	@Override
	public ByteBuf readBytes(ByteBuf dst) {
		readBytes(dst, dst.writableBytes());
		return this;
	}

	@Override
	public ByteBuf readBytes(ByteBuf dst, int length) {
		if (length > dst.writableBytes()) {
			throw new IndexOutOfBoundsException(String.format(
					"length(%d) exceeds dst.writableBytes(%d) where dst is: %s", length, dst.writableBytes(), dst));
		}
		readBytes(dst, dst.writerIndex(), length);
		dst.writerIndex(dst.writerIndex() + length);
		return this;
	}

	@Override
	public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
		checkReadableBytes0(length);
		getBytes(readerIndex, dst, dstIndex, length);
		readerIndex += length;
		return this;
	}

	@Override
	public ByteBuf readBytes(ByteBuffer dst) {
		int length = dst.remaining();
		checkReadableBytes0(length);
		getBytes(readerIndex, dst);
		readerIndex += length;
		return this;
	}

	@Override
	public ByteBuf readBytes(OutputStream out, int length) throws IOException {
		checkReadableBytes0(length);
		getBytes(readerIndex, out, length);
		readerIndex += length;
		return this;
	}

	@Override
	public ByteBuf writeBytes(int value) {
		ensureAccessible();
		_setByte(writerIndex++, value);
		return this;
	}

	@Override
	public ByteBuf writeInt(int value) {
		ensureAccessible();
		_setInt(writerIndex, value);
		writerIndex += 4;
		return this;
	}

	@Override
	public ByteBuf writeBytes(byte[] src) {
		writeBytes(src, 0, src.length);
		return this;
	}

	@Override
	public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
		ensureAccessible();
		setBytes(writerIndex, src, srcIndex, length);
		writerIndex += length;
		return this;
	}

	@Override
	public ByteBuf writeBytes(ByteBuf src) {
		writeBytes(src, src.readableBytes());
		return this;
	}

	@Override
	public ByteBuf writeBytes(ByteBuf src, int length) {
		if (length > src.readableBytes()) {
			throw new IndexOutOfBoundsException(String.format(
					"length(%d) exceeds src.readableBytes(%d) where src is: %s", length, src.readableBytes(), src));
		}
		writeBytes(src, src.readerIndex(), length);
		src.readerIndex(src.readerIndex() + length);
		return this;
	}

	@Override
	public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
		ensureAccessible();
		setBytes(writerIndex, src, srcIndex, length);
		writerIndex += length;
		return this;
	}

	@Override
	public ByteBuf writeBytes(ByteBuffer src) {
		ensureAccessible();
		int length = src.remaining();
		setBytes(writerIndex, src);
		writerIndex += length;
		return this;
	}

	@Override
	public int writeBytes(InputStream in, int length) throws IOException {
		ensureWritable(length);
		int writtenBytes = setBytes(writerIndex, in, length);
		if (writtenBytes > 0) {
			writerIndex += writtenBytes;
		}
		return writtenBytes;
	}

	@Override
	public ByteBuf copy() {
		return copy(readerIndex, readableBytes());
	}

	@Override
	public ByteBuffer nioBuffer() {
		ByteBuffer buffer = nioBuffer(readerIndex, readableBytes());
		byteBuffer = buffer;
		return buffer;
	}

	@Override
	public ByteBuffer[] nioBuffers() {
		return nioBuffers(readerIndex, readableBytes());
	}

	@Override
	public int indexOf(int fromIndex, int toIndex, byte value) {
		return ByteBufUtil.indexOf(this, fromIndex, toIndex, value);
	}

	@Override
	public int bytesBefore(int length, byte value) {
		return bytesBefore(readerIndex(), readableBytes(), value);
	}

	@Override
	public int bytesBefore(int index, int length, byte value) {
		int endIndex = indexOf(index, index + length, value);
		if (endIndex < 0) {
			return -1;
		}
		return endIndex - index;
	}

	@Override
	public boolean equals(Object o) {
		return o instanceof ByteBuf && ByteBufUtil.equals(this, (ByteBuf) o);
	}

	@Override
	public int hashCode() {
		return ByteBufUtil.hashCode(this);
	}

	@Override
	public String toString() {
		if (refCnt() == 0) {
			return StringUtil.simpleClassName(this) + "(freed)";
		}

		StringBuilder buf = new StringBuilder()
				.append(StringUtil.simpleClassName(this))
				.append("(ridx: ").append(readerIndex)
				.append(", widx: ").append(writerIndex)
				.append(", cap: ").append(capacity());
		if (maxCapacity != Integer.MAX_VALUE) {
			buf.append('/').append(maxCapacity);
		}

		ByteBuf unwrapped = unwrap();
		if (unwrapped != null) {
			buf.append(", unwrapped: ").append(unwrapped);
		}
		buf.append(')');
		return buf.toString();
	}

	private void checkReadableBytes0(int minimumReadableBytes) {
		ensureAccessible();
		if (readerIndex > writerIndex - minimumReadableBytes) {
			throw new IndexOutOfBoundsException(String.format(
					"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
					readerIndex, minimumReadableBytes, writerIndex, this));
		}
	}

	protected final void checkInt(int index) {
		checkInt(index, 1);
	}

	protected final void checkIndex(int index, int fieldLength) {
		ensureAccessible();
		checkRangeBounds("index", index, fieldLength, capacity());
	}

	protected final void checkInt(int index, int length) {
		ensureAccessible();
		checkRangeBounds("index", index, length, capacity());
	}

	protected final void ensureAccessible() {
		if (!isAccessible()) {
			throw new IllegalReferenceCountException(0);
		}
	}

}
